package cn.s1995.sloth.bizs.service.common.impl;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.s1995.sloth.bizs.constant.BizCst;
import cn.s1995.sloth.bizs.constant.RegCst;
import cn.s1995.sloth.bizs.constant.SqlCst;
import cn.s1995.sloth.bizs.constant.TimeCst;
import cn.s1995.sloth.bizs.entity.*;
import cn.s1995.sloth.bizs.enums.MailTemplateEnum;
import cn.s1995.sloth.bizs.exception.BizException;
import cn.s1995.sloth.bizs.oo.req.VerifyREQ;
import cn.s1995.sloth.bizs.oo.req.WorldREQ;
import cn.s1995.sloth.bizs.service.*;
import cn.s1995.sloth.bizs.service.common.CacheService;
import cn.s1995.sloth.bizs.service.common.CommonService;
import cn.s1995.sloth.bizs.service.common.MailService;
import cn.s1995.sloth.bizs.util.sms.AliSms;
import cn.s1995.sloth.common.core.constant.SecurityConstants;
import cn.s1995.sloth.common.core.constant.enums.hint.Hint;
import cn.s1995.sloth.common.core.util.R;
import cn.s1995.sloth.common.core.util.StringUtil;
import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsResponseBody;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.MailSender;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.regex.Pattern;

/**
 * CommonServiceImpl
 *
 * @Author penf
 * @Description
 * @Date 2022/11/11 21:21
 */
@Slf4j
@Service
public class CommonServiceImpl implements CommonService {

    @Value("${spring.mail.username}")
    private String mailUsername;
    @Resource
    private AliSms aliSms;
    @Resource
    private CacheService cacheService;
    @Resource
    private MailSender mailSender;
    @Resource
    private MessageTemplateService messageTemplateService;
    @Resource
    private WorldRegionsService worldRegionsService;
    @Resource
    private WorldSubregionsService worldSubregionsService;
    @Resource
    private WorldCountriesService worldCountriesService;
    @Resource
    private WorldStatesService worldStatesService;
    @Resource
    private WorldCitiesService worldCitiesService;
    @Resource
    private MailService mailService;


    @Override
    public R verifySendCode(VerifyREQ req) {
        // 验证参数
        checkVerifyParam(req, Boolean.FALSE);
        switch (req.getType()) {
            case BizCst.VerifyCode.Type.email -> {
                return emailSend(req);
            }

            case BizCst.VerifyCode.Type.sms -> {
                Integer scene = req.getScene();
                Integer code = req.getCode();
                return smsSend(scene, req.getMobile(), code);
            }
            default -> {
                return R.failed(Hint.NON_SMS_TYPE);
            }
        }
    }


    @Override
    public R verifyCheck(VerifyREQ req) {
        checkVerifyParam(req, Boolean.TRUE);

        String verificationCode = req.getVerificationCode();
        switch (req.getType()) {
            case BizCst.VerifyCode.Type.email -> {
                Integer scene = req.getScene();
                String val = req.getEmail();
                verifyCodeValid(scene, val, verificationCode);
                return R.ok();
            }

            case BizCst.VerifyCode.Type.sms -> {
                Integer scene = req.getScene();
                String val = req.getCode() + req.getMobile();
                verifyCodeValid(scene, val, verificationCode);
                return R.ok();
            }
            default -> {
                return R.failed(Hint.NON_SMS_TYPE);
            }
        }
    }

    @Override
    public List getWorldData(WorldREQ req) {
        switch (req.getScene()) {
            case BizCst.World.Scene.region -> {
                return worldRegionsService.list(
                        Wrappers.lambdaQuery(WorldRegions.class)
                                .and(
                                        StrUtil.isNotEmpty(req.getSearch()),
                                        i -> i.like(WorldRegions::getName, req.getSearch())
                                                .or()
                                                .like(WorldRegions::getTranslations, req.getSearch())
                                )
                                .last(Objects.nonNull(req.getLimitNum()), SqlCst.getLimitTarget(req.getLimitNum()))
                );
            }
            case BizCst.World.Scene.subregion -> {
                return worldSubregionsService.list(
                        Wrappers.lambdaQuery(WorldSubregions.class)
                                .and(
                                        StrUtil.isNotEmpty(req.getSearch()),
                                        i -> i.like(WorldSubregions::getName, req.getSearch())
                                                .or()
                                                .like(WorldSubregions::getTranslations, req.getSearch())
                                )
                                .eq(StrUtil.isNotEmpty(req.getRelationId()), WorldSubregions::getRegionId, req.getRelationId())
                                .last(Objects.nonNull(req.getLimitNum()), SqlCst.getLimitTarget(req.getLimitNum()))
                );
            }
            case BizCst.World.Scene.country -> {
                return worldCountriesService.list(
                        Wrappers.lambdaQuery(WorldCountries.class)
                                .and(
                                        StrUtil.isNotEmpty(req.getSearch()),
                                        i -> i.like(WorldCountries::getName, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getTranslations, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getIso2, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getIso3, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getNumericCode, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getPhonecode, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getCapital, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getCurrency, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getCurrencyName, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getCurrencySymbol, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getTld, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getNatives, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getRegion, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getSubregion, req.getSearch())
                                                .or()
                                                .like(WorldCountries::getTimezones, req.getSearch())
                                )
                                .eq(StrUtil.isNotEmpty(req.getRelationId()), WorldCountries::getId, req.getRelationId())
                                .last(Objects.nonNull(req.getLimitNum()), SqlCst.getLimitTarget(req.getLimitNum()))
                );
            }
            case BizCst.World.Scene.state -> {
                return worldStatesService.list(
                        Wrappers.lambdaQuery(WorldStates.class)
                                .and(
                                        StrUtil.isNotEmpty(req.getSearch()),
                                        i -> i.like(WorldStates::getName, req.getSearch())
                                                .or()
                                                .like(WorldStates::getCountryCode, req.getSearch())
                                )
                                .eq(StrUtil.isNotEmpty(req.getRelationId()), WorldStates::getCountryId, req.getRelationId())
                                .last(Objects.nonNull(req.getLimitNum()), SqlCst.getLimitTarget(req.getLimitNum()))
                );
            }
            case BizCst.World.Scene.city -> {
                return worldCitiesService.list(
                        Wrappers.lambdaQuery(WorldCities.class)
                                .and(
                                        StrUtil.isNotEmpty(req.getSearch()),
                                        i -> i.like(WorldCities::getName, req.getSearch())
                                                .or()
                                                .like(WorldCities::getStateCode, req.getSearch())
                                                .or()
                                                .like(WorldCities::getCountryCode, req.getSearch())
                                )
                                .eq(StrUtil.isNotEmpty(req.getRelationId()), WorldCities::getStateId, req.getRelationId())
                                .last(Objects.nonNull(req.getLimitNum()), SqlCst.getLimitTarget(req.getLimitNum()))
                );
            }
            default -> {
                return new ArrayList();
            }
        }
    }

    /**
     * 验证参数
     *
     * @param req
     * @param isCheck 是否验证验证码
     */
    @NotNull
    private void checkVerifyParam(VerifyREQ req, boolean isCheck) {
        Integer type = req.getType();
        Integer scene = req.getScene();
        Integer code = req.getCode();
        String mobile = req.getMobile();
        String email = req.getEmail();
        if (type == null) {
            throw new IllegalArgumentException("type param is not null");
        }
        if (scene == null) {
            throw new IllegalArgumentException("scene param is not null");
        }

        if (isCheck && StrUtil.isEmpty(req.getVerificationCode())) {
            throw new IllegalArgumentException("verificationCode param is not null");
        }

        if (BizCst.VerifyCode.Type.email == type) {
            if (StrUtil.isEmpty(email)) {
                throw new IllegalArgumentException("email param is not null");
            }
            if (!Pattern.matches(RegCst.EMAIL, email)) {
                throw new IllegalArgumentException("email format error");
            }
        }
        if (BizCst.VerifyCode.Type.sms == type) {
            if (code == null) {
                throw new IllegalArgumentException("code param is not null");
            }
            if (StrUtil.isEmpty(mobile)) {
                throw new IllegalArgumentException("mobile param is not null");
            }
        }
    }

    /**
     * 邮箱发送
     *
     * @param req
     * @return
     */
    @NotNull
    private R<String> emailSend(VerifyREQ req) {
        String resCode = RandomUtil.randomNumbers(Integer.parseInt(SecurityConstants.CODE_SIZE));
        Map<Integer, MailTemplateEnum> map = MapUtil.builder(new HashMap<Integer, MailTemplateEnum>())
                .put(BizCst.VerifyCode.Scene.register, MailTemplateEnum.REGISTER)
                .put(BizCst.VerifyCode.Scene.forgetPassword, MailTemplateEnum.FORGET_PASSWORD)
                .put(BizCst.VerifyCode.Scene.updatePassword, MailTemplateEnum.UPDATE_PASSWORD)
                .put(BizCst.VerifyCode.Scene.updateMobile, MailTemplateEnum.UPDATE_MOBILE)
                .build();
        MessageTemplate messageTemplate = messageTemplateService.getMessageTemplate(map.get(req.getScene()).getId());
        mailService.sendSimpleText(req.getEmail(), messageTemplate.getTitle().langFormat(null), messageTemplate.getContent().langFormat(resCode));
        cacheService.setVerifyCode(req.getScene(), req.getEmail(), resCode);
        return R.ok(resCode);
    }

    /**
     * 短信发送
     *
     * @param scene
     * @param mobile
     * @param code
     * @return
     */
    @NotNull
    private R<?> smsSend(Integer scene, String mobile, Integer code) {
        String disabledSms = cacheService.getDisabledSms(scene, mobile);
        long time60second = TimeCst.TIME_60SECOND;
        if (StrUtil.isNotEmpty(disabledSms)) {
            // return R.failed(Hint.NON_SMS_FREQUENTLY_SEND, time60second);
            throw new BizException(Hint.NON_VERIFY_FREQUENTLY_SEND, time60second);
        }
        Long smsSendNum = cacheService.getSmsSendNum(scene, mobile);
        long smsSendNumLimit = cacheService.getSmsSendNumLimit();
        // 验证码30分钟内输入错误次数大于等于N次
        if (smsSendNum != null && smsSendNum.longValue() >= smsSendNumLimit) {
            cacheService.setDisabledSms(scene, mobile);
        }
        String smsCode = cacheService.getVerifyCode(scene, mobile);
        // 是否频繁发送(存在验证码&&过期时间在一分钟内)
        if (StrUtil.isNotEmpty(smsCode)) {
            long expire = cacheService.getSmsExpire(scene, mobile);
            if ((TimeCst.TIME_300SECOND - expire) < time60second) {
                // return R.failed(Hint.NON_SMS_FREQUENTLY_SEND, time60second);
                throw new BizException(Hint.NON_VERIFY_FREQUENTLY_SEND, time60second);
            }
        }
        String resCode = RandomUtil.randomNumbers(Integer.parseInt(SecurityConstants.CODE_SIZE));
        // 短信通道是否打开
        if (cacheService.isEnableSMS()) {
            SendSmsResponseBody respBody = aliSms.sendSmsForVerificationCode(code, mobile, resCode);
            if (!AliSms.OK.equals(respBody.getCode())) {
                // 短信发送失败，可发送邮件提醒短信失败原因
                mailService.sendSimpleText(mailUsername, "短信发送失败", respBody.getMessage());
                return R.failed(respBody.getMessage());
            }
        }
        cacheService.setVerifyCode(scene, code + mobile, resCode);
        cacheService.setSmsSendNum(scene, mobile);
        return R.ok(resCode);
    }

    /**
     * 验证码校验
     *
     * @param scene
     * @param val
     * @param verificationCode
     */
    private void verifyCodeValid(Integer scene, String val, String verificationCode) {
        String verifyCode = cacheService.getVerifyCode(scene, val);
        if (StringUtil.isEmpty(verifyCode)) {
            // return R.failed(Hint.NON_SMS_CODE_EXPIRED);
            throw new BizException(Hint.NON_VERIFY_CODE_EXPIRED);
        }
        if (!verificationCode.equals(verifyCode)) {
            // return R.failed(Hint.NON_SMS_CODE_ERROR);
            throw new BizException(Hint.NON_VERIFY_CODE_ERROR);
        }

        cacheService.removeVerifyCode(scene, val);
        cacheService.removeSmsSendNum(scene, val);
    }


}
